package simulation;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Set;

public class Simulator {
	public static int[] registerFile = { 0, 0, 0, 0, 0, 0, 0, 0 };
	public int pc;
	public static String[] program;
	public static Data[] data = {};
	public int completedInstructions = 0;
	public int cycles = 0;
	public static int cacheLevels;
	public static int memoryCapacity;
	public static int memoryAccessTime;
	public static int programStartingAddress;
	public static Cache[] caches;
	public static ReservationStation reservationStations;
	public static ROB rob;
	public static int ways;
	public static boolean addTime = false;
	public static Hashtable<Integer, Object> memory = new Hashtable<Integer, Object>();
	public static Hashtable<String, Integer> map = new Hashtable<String, Integer>();
	public static ArrayList<ArrayList<String>> registerMap = new ArrayList<ArrayList<String>>(
			10);
	public static LinkedList<String> emptyReigsters = new LinkedList<String>();
	public static ArrayList<Integer> renamingRegisters = new ArrayList<Integer>(
			7);
	public static LinkedList<Integer> emptyRegistersQueue = new LinkedList<Integer>();

	public static int[] getRegisterFile() {
		return registerFile;
	}

	public static void setRegisterFile(int[] registerFile) {
		Simulator.registerFile = registerFile;
	}

	public int getPc() {
		return pc;
	}

	public void setPc(int pc) {
		this.pc = pc;
	}

	public static String[] getProgram() {
		return program;
	}

	public static void setProgram(String[] program) {
		Simulator.program = program;
	}

	public static Data[] getData() {
		return data;
	}

	public static void setData(Data[] data) {
		Simulator.data = data;
	}

	public int getCompletedInstructions() {
		return completedInstructions;
	}

	public void setCompletedInstructions(int completedInstructions) {
		this.completedInstructions = completedInstructions;
	}

	public int getCycles() {
		return cycles;
	}

	public void setCycles(int cycles) {
		this.cycles = cycles;
	}

	public static int getMemoryCapacity() {
		return memoryCapacity;
	}

	public static void setMemoryCapacity(int memoryCapacity) {
		Simulator.memoryCapacity = memoryCapacity;
	}

	public static int getMemoryAccessTime() {
		return memoryAccessTime;
	}

	public static void setMemoryAccessTime(int memoryAccessTime) {
		Simulator.memoryAccessTime = memoryAccessTime;
	}

	public static int getProgramStartingAddress() {
		return programStartingAddress;
	}

	public static void setProgramStartingAddress(int programStartingAddress) {
		Simulator.programStartingAddress = programStartingAddress;
	}

	public static Cache[] getCaches() {
		return caches;
	}

	public static void setCaches(Cache[] caches) {
		Simulator.caches = caches;
	}

	public static ReservationStation getReservationStations() {
		return reservationStations;
	}

	public static void setReservationStations(
			ReservationStation reservationStations) {
		Simulator.reservationStations = reservationStations;
	}

	public static ROB getRob() {
		return rob;
	}

	public static void setRob(ROB rob) {
		Simulator.rob = rob;
	}

	public static boolean isAddTime() {
		return addTime;
	}

	public static void setAddTime(boolean addTime) {
		Simulator.addTime = addTime;
	}

	public static Hashtable<Integer, Object> getMemory() {
		return memory;
	}

	public static void setMemory(Hashtable<Integer, Object> memory) {
		Simulator.memory = memory;
	}

	public static Hashtable<String, Integer> getMap() {
		return map;
	}

	public static void setMap(Hashtable<String, Integer> map) {
		Simulator.map = map;
	}

	public static LinkedList<String> getEmptyReigsters() {
		return emptyReigsters;
	}

	public static void setEmptyReigsters(LinkedList<String> emptyReigsters) {
		Simulator.emptyReigsters = emptyReigsters;
	}

	public static ArrayList<Integer> getRenamingRegisters() {
		return renamingRegisters;
	}

	public static void setRenamingRegisters(ArrayList<Integer> renamingRegisters) {
		Simulator.renamingRegisters = renamingRegisters;
	}

	public static LinkedList<Integer> getEmptyRegistersQueue() {
		return emptyRegistersQueue;
	}

	public static void setEmptyRegistersQueue(
			LinkedList<Integer> emptyRegistersQueue) {
		Simulator.emptyRegistersQueue = emptyRegistersQueue;
	}

	public Simulator() {
		init();
	}

	public static int getCacheLevels() {
		return cacheLevels;
	}

	public static void setCacheLevels(int cacheLevels) {
		Simulator.cacheLevels = cacheLevels;
	}

	public static void initializeArray(int size) {
		program = new String[size];
	}

	public void init() {
		map.put("R0", 0);
		map.put("R1", 1);
		map.put("R2", 2);
		map.put("R3", 3);
		map.put("R4", 4);
		map.put("R5", 5);
		map.put("R6", 6);
		map.put("R7", 7);

		for (int i = 0; i < renamingRegisters.size(); i++) {
			renamingRegisters.set(i, i);
		}
		for (int i = 0; i < 10; i++) {
			emptyRegistersQueue.add(i + 7);
		}
	}

	public void execute(String instruction) {
		String[] inst = instruction.split(" ");
		String opcode = inst[0];
		String[] operands = inst[1].split(",");

		if (opcode.equals("ADD")) {
			registerFile[map.get(operands[0])] = registerFile[map
					.get(operands[1])] + registerFile[map.get(operands[2])];
			return;
		}
		if (opcode.equals("ADDI")) {
			registerFile[map.get(operands[0])] = registerFile[map
					.get(operands[1])] + Integer.parseInt(operands[2]);
			return;
		}
		if (opcode.equals("NAND")) {
			registerFile[map.get(operands[0])] = ~(registerFile[map
					.get(operands[1])] & registerFile[map.get(operands[2])]);
			return;
		}
		if (opcode.equals("MUL")) {
			registerFile[map.get(operands[0])] = registerFile[map
					.get(operands[1])] * registerFile[map.get(operands[2])];
			return;
		}
		if (opcode.equals("DIV")) {
			registerFile[map.get(operands[0])] = registerFile[map
					.get(operands[1])] / registerFile[map.get(operands[2])];
			return;
		}
		if (opcode.equals("LW")) {
			registerFile[map.get(operands[0])] = (Integer) memory
					.get(registerFile[map.get(operands[1])]
							+ Integer.parseInt(operands[2]));
			return;
		}
		if (opcode.equals("SW")) {
			memory.put(
					registerFile[map.get(operands[1])]
							+ Integer.parseInt(operands[2]),
					registerFile[map.get(operands[0])]);
			return;
		}
		if (opcode.equals("JMP")) {
			pc += registerFile[map.get(operands[0])]
					+ Integer.parseInt(operands[1]);
			return;
		}
		if (opcode.equals("BEQ")) {
			if (registerFile[map.get(operands[0])] == registerFile[map
					.get(operands[1])]) {
				pc += Integer.parseInt(operands[2]); // not incremented here by
														// 1
			}
			return;
		}
		if (opcode.equals("JALR")) {
			registerFile[map.get(operands[0])] = pc + 1;
			pc = registerFile[map.get(operands[1])];
			return;
		}
		if (opcode.equals("RET")) {
			pc = registerFile[map.get(operands[0])];
			return;
		}
	}

	public static void storeProgramAndData() {
		int k = 0;
		for (int i = programStartingAddress; i < program.length
				+ programStartingAddress
				&& i < memoryCapacity / 2; i++) {
			memory.put(i, program[k]);
			k++;
		}
		for (int j = 0; j < data.length && j < memoryCapacity / 2; j++) {
			memory.put(data[j].getAddress(), data[j].getValue());
		}
	}

	public static Integer[] toArray(ArrayList<Integer> values) {
		Integer[] array = new Integer[values.size()];
		for (int i = 0; i < values.size(); i++) {
			array[i] = values.get(i);
		}
		return array;
	}

	public static void cacheMemoryBlock(int startingAddress) {
		if (cacheLevels == 3) {
			int startingAddress2 = getStarting(startingAddress, 3);
			ArrayList<Integer> values33 = new ArrayList<Integer>();
			for (int i = 0; i < caches[3].lineSize / 2; i++) {
				if (memory.containsKey(startingAddress2)) {
					values33.add(startingAddress2);
				}
				startingAddress2++;
			}
			Integer[] values3 = toArray(values33);
			int line = caches[3].getCacheLine(startingAddress);
			caches[3].data.put(line, values3);
			caches[3].usage[line]++;
			if (caches[3].dirtyBits[line] == 1) {
				caches[3].dirtyBits[line] = 0;
				addTime = true;
			}
			startingAddress2 = getStarting(startingAddress, 2);
			ArrayList<Integer> values22 = new ArrayList<Integer>();
			for (int j = 0; j < caches[2].lineSize / 2; j++) {
				if (memory.containsKey(startingAddress2)) {
					values22.add(startingAddress2);
				}
				startingAddress2++;

			}
			Integer[] values2 = toArray(values22);
			line = caches[2].getCacheLine(startingAddress);
			caches[2].data.put(line, values2);
			caches[2].usage[line]++;
			if (caches[2].dirtyBits[line] == 1) {
				caches[2].dirtyBits[line] = 0;
				addTime = true;
			}
			startingAddress2 = getStarting(startingAddress, 1);
			ArrayList<Integer> values11 = new ArrayList<Integer>();
			int temp = 0;
			for (int k = 0; k < caches[1].lineSize / 2; k++) {
				if (memory.containsKey(startingAddress2)) {
					if (startingAddress2 >= program.length
							+ programStartingAddress
							|| startingAddress2 < programStartingAddress) {
						values11.add(startingAddress2);
						temp++;
					}

				}
				startingAddress2++;
			}
			if (temp > 0) {
				Integer[] values1 = toArray(values11);
				line = caches[1].getCacheLine(startingAddress);
				caches[1].data.put(line, values1);
				caches[1].usage[line]++;
				if (caches[1].dirtyBits[line] == 1) {
					caches[1].dirtyBits[line] = 0;
					addTime = true;
				}
			}
			temp = 0;
			startingAddress2 = getStarting(startingAddress, 0);
			ArrayList<Integer> values00 = new ArrayList<Integer>();
			for (int l = 0; l < caches[0].lineSize / 2; l++) {
				if (memory.containsKey(startingAddress2)) {
					if (startingAddress2 < program.length
							+ programStartingAddress
							&& startingAddress2 >= programStartingAddress) {
						values00.add(startingAddress2);
						temp++;
					}
				}
				startingAddress2++;
			}
			if (temp > 0) {
				Integer[] values0 = toArray(values00);
				line = caches[0].getCacheLine(startingAddress);
				caches[0].data.put(line, values0);
				caches[0].usage[line]++;
				if (caches[0].dirtyBits[line] == 1) {
					caches[0].dirtyBits[line] = 0;
					addTime = true;
				}
			}
		}
		if (cacheLevels == 2) {
			int startingAddress2 = getStarting(startingAddress, 2);
			ArrayList<Integer> values22 = new ArrayList<Integer>();
			for (int j = 0; j < caches[2].lineSize / 2; j++) {
				if (memory.containsKey(startingAddress2)) {
					values22.add(startingAddress2);
				}
				startingAddress2++;

			}
			Integer[] values2 = toArray(values22);
			int line = caches[2].getCacheLine(startingAddress);
			caches[2].data.put(line, values2);
			caches[2].usage[line]++;
			if (caches[2].dirtyBits[line] == 1) {
				caches[2].dirtyBits[line] = 0;
				addTime = true;
			}
			startingAddress2 = getStarting(startingAddress, 1);
			ArrayList<Integer> values11 = new ArrayList<Integer>();
			int temp = 0;
			for (int k = 0; k < caches[1].lineSize / 2; k++) {
				if (memory.containsKey(startingAddress2)) {
					if (startingAddress2 >= program.length
							+ programStartingAddress
							|| startingAddress2 < programStartingAddress) {
						values11.add(startingAddress2);
						temp++;
					}

				}
				startingAddress2++;
			}
			if (temp > 0) {
				Integer[] values1 = toArray(values11);
				line = caches[1].getCacheLine(startingAddress);
				caches[1].data.put(line, values1);
				caches[1].usage[line]++;
				if (caches[1].dirtyBits[line] == 1) {
					caches[1].dirtyBits[line] = 0;
					addTime = true;
				}
			}
			temp = 0;
			startingAddress2 = getStarting(startingAddress, 0);
			ArrayList<Integer> values00 = new ArrayList<Integer>();
			for (int l = 0; l < caches[0].lineSize / 2; l++) {
				if (memory.containsKey(startingAddress2)) {
					if (startingAddress2 < program.length
							+ programStartingAddress
							&& startingAddress2 >= programStartingAddress) {
						values00.add(startingAddress2);
						temp++;
					}
				}
				startingAddress2++;
			}
			if (temp > 0) {
				Integer[] values0 = toArray(values00);
				line = caches[0].getCacheLine(startingAddress);
				caches[0].data.put(line, values0);
				caches[0].usage[line]++;
				if (caches[0].dirtyBits[line] == 1) {
					caches[0].dirtyBits[line] = 0;
					addTime = true;
				}
			}
		}
		if (cacheLevels == 1) {
			int startingAddress2 = getStarting(startingAddress, 1);
			ArrayList<Integer> values11 = new ArrayList<Integer>();
			int temp = 0;
			for (int k = 0; k < caches[1].lineSize / 2; k++) {
				if (memory.containsKey(startingAddress2)) {
					if (startingAddress2 >= program.length
							+ programStartingAddress
							|| startingAddress2 < programStartingAddress) {
						values11.add(startingAddress2);
						temp++;
					}

				}
				startingAddress2++;
			}
			if (temp > 0) {
				Integer[] values1 = toArray(values11);
				int line = caches[1].getCacheLine(startingAddress);
				caches[1].data.put(line, values1);
				caches[1].usage[line]++;
				if (caches[1].dirtyBits[line] == 1) {
					caches[1].dirtyBits[line] = 0;
					addTime = true;
				}
			}
			temp = 0;
			startingAddress2 = getStarting(startingAddress, 0);
			ArrayList<Integer> values00 = new ArrayList<Integer>();
			for (int l = 0; l < caches[0].lineSize / 2; l++) {
				if (memory.containsKey(startingAddress2)) {
					if (startingAddress2 < program.length
							+ programStartingAddress
							&& startingAddress2 >= programStartingAddress) {
						values00.add(startingAddress2);
						temp++;
					}
				}
				startingAddress2++;
			}
			if (temp > 0) {
				Integer[] values0 = toArray(values00);
				int line = caches[0].getCacheLine(startingAddress);
				caches[0].data.put(line, values0);
				caches[0].usage[line]++;
				if (caches[0].dirtyBits[line] == 1) {
					caches[0].dirtyBits[line] = 0;
					addTime = true;
				}
			}
		}
	}

	public static Object[] searchCache(int memoryAddress) {
		Object[] timeAndValue = new Object[2];
		boolean found = false;
		for (int i = 0; i < caches.length && !found; i++) {
			Set<Integer> keys = caches[i].data.keySet();
			Object[] lineNumbers = keys.toArray();
			for (int j = 0; j < lineNumbers.length && !found; j++) {
				for (int k = 0; k < caches[i].data.get(lineNumbers[j]).length
						&& !found; k++) {
					if (caches[i].data.get(lineNumbers[j])[k] == memoryAddress) {
						found = true;
						timeAndValue[0] = computeTime(i);
						timeAndValue[1] = memory
								.get(new Integer(memoryAddress));
						addFromCacheToCache(memoryAddress, i, j);
						caches[i].usage[((Integer) lineNumbers[j]).intValue()]++;
						computeAccessesAndMisses(memoryAddress, i);
					}
				}
			}
		}
		if (!found) {
			cacheMemoryBlock(memoryAddress);
			timeAndValue[0] = (Integer) computeTime(cacheLevels)
					+ memoryAccessTime;
			timeAndValue[1] = memory.get(new Integer(memoryAddress));
			computeAccessesAndMisses(memoryAddress, 4);
		}
		if (addTime) {
			timeAndValue[0] = (Integer) timeAndValue[0] + memoryAccessTime;
			addTime = false;
		}
		return timeAndValue;
	}

	public static void computeAccessesAndMisses(int memoryAddress, int level) {
		int index;
		if (memoryAddress >= program.length + programStartingAddress
				|| memoryAddress < programStartingAddress)
			index = 1;
		else
			index = 0;
		if (level == 0 || level == 1) {
			caches[index].accesses++;
		}
		if (level == 2) {
			caches[index].accesses++;
			caches[index].misses++;
			caches[2].accesses++;
		}
		if (level == 3) {
			caches[index].accesses++;
			caches[index].misses++;
			caches[2].accesses++;
			caches[2].misses++;
			caches[3].accesses++;
		}
		if (level == 4) {
			switch (cacheLevels) {
			case 1:
				caches[index].accesses++;
				caches[index].misses++;
				break;
			case 2:
				caches[index].accesses++;
				caches[index].misses++;
				caches[2].accesses++;
				caches[2].misses++;
				break;
			case 3:
				caches[index].accesses++;
				caches[index].misses++;
				caches[2].accesses++;
				caches[2].misses++;
				caches[3].accesses++;
				caches[3].misses++;
				break;
			default:break;
			}
		}
	}

	public static Object computeTime(int level) {
		if (level == 0 || level == 1) {
			return caches[0].cacheAccessTime;
		} else {
			if (level == 2) {
				return caches[0].cacheAccessTime + caches[2].cacheAccessTime;
			} else {
				return caches[0].cacheAccessTime + caches[2].cacheAccessTime
						+ caches[3].cacheAccessTime;
			}
		}
	}

	public static int getStarting(int memoryAddress, int level) {
		int factor = memoryAddress / (caches[level].lineSize / 2);
		int start = caches[level].lineSize * factor / 2;
		return start;
	}

	public static int getIndex(int address, Integer[] array) {
		for (int i = 0; i < array.length; i++) {
			if (array[i] == address)
				return i;
		}
		return -1;
	}

	public static void addFromCacheToCache(int memoryAddress, int level, int j) {
		if (level == 2) {
			int memoryAddress2 = getStarting(memoryAddress, 1);
			Set<Integer> keys = caches[level].data.keySet();
			Object[] lineNumbers = keys.toArray();
			int k = getIndex(memoryAddress2,
					caches[level].data.get(lineNumbers[j]));
			ArrayList<Integer> values11 = new ArrayList<Integer>();
			int temp = 0;
			for (int i = 0; i < caches[1].lineSize / 2
					&& k < caches[level].data.get(lineNumbers[j]).length; i++) {
				if (memoryAddress2 >= program.length + programStartingAddress
						|| memoryAddress2 < programStartingAddress) {
					values11.add(memoryAddress2);
					memoryAddress2++;
					temp++;
				}
			}
			if (temp > 0) {
				Integer[] values1 = toArray(values11);
				int line = caches[1].getCacheLine(memoryAddress);
				caches[1].data.put(line, values1);
				caches[1].usage[line]++;
				if (caches[1].dirtyBits[line] == 1) {
					caches[1].dirtyBits[line] = 0;
					addTime = true;
				}
			}
			temp = 0;
			memoryAddress2 = getStarting(memoryAddress, 0);
			ArrayList<Integer> values00 = new ArrayList<Integer>();
			for (int l = 0; l < caches[0].lineSize / 2; l++) {
				if (memory.containsKey(memoryAddress2)) {
					if (memoryAddress2 < program.length
							+ programStartingAddress
							&& memoryAddress2 >= programStartingAddress) {
						values00.add(memoryAddress2);
						temp++;
					}
				}
				memoryAddress2++;
			}
			if (temp > 0) {
				Integer[] values0 = toArray(values00);
				int line = caches[0].getCacheLine(memoryAddress);
				caches[0].data.put(line, values0);
				caches[0].usage[line]++;
				if (caches[0].dirtyBits[line] == 1) {
					caches[0].dirtyBits[line] = 0;
					addTime = true;
				}
			}
		}
		if (level == 3) {
			int memoryAddress2 = getStarting(memoryAddress, 2);
			Set<Integer> keys = caches[level].data.keySet();
			Object[] lineNumbers = keys.toArray();
			int k = getIndex(memoryAddress2,
					caches[level].data.get(lineNumbers[j]));
			ArrayList<Integer> values22 = new ArrayList<Integer>();
			for (int i = 0; i < caches[2].lineSize / 2
					&& k < caches[level].data.get(lineNumbers[j]).length; i++) {
				if (memory.containsKey(memoryAddress2)) {
					values22.add(memoryAddress2);
				}
				memoryAddress2++;
			}
			Integer[] values2 = toArray(values22);
			int line = caches[2].getCacheLine(memoryAddress);
			caches[2].data.put(line, values2);
			caches[2].usage[line]++;
			if (caches[2].dirtyBits[line] == 1) {
				caches[2].dirtyBits[line] = 0;
				addTime = true;
			}
			int temp = 0;
			memoryAddress2 = getStarting(memoryAddress, 1);
			ArrayList<Integer> values11 = new ArrayList<Integer>();
			for (int m = 0; m < caches[1].lineSize / 2; m++) {
				if (memory.containsKey(memoryAddress2)) {
					if (memoryAddress2 >= program.length
							+ programStartingAddress
							&& memoryAddress2 < programStartingAddress) {
						values11.add(memoryAddress2);
						temp++;
					}
				}
				memoryAddress2++;
			}
			if (temp > 0) {
				Integer[] values1 = toArray(values11);
				line = caches[1].getCacheLine(memoryAddress);
				caches[1].data.put(line, values1);
				caches[1].usage[line]++;
				if (caches[1].dirtyBits[line] == 1) {
					caches[1].dirtyBits[line] = 0;
					addTime = true;
				}
			}
			temp = 0;
			memoryAddress2 = getStarting(memoryAddress, 0);
			ArrayList<Integer> values00 = new ArrayList<Integer>();
			for (int l = 0; l < caches[0].lineSize / 2; l++) {
				if (memory.containsKey(memoryAddress2)) {
					if (memoryAddress2 < program.length
							+ programStartingAddress
							&& memoryAddress2 >= programStartingAddress) {
						values00.add(memoryAddress2);
						temp++;
					}
				}
				memoryAddress2++;
			}
			if (temp > 0) {
				Integer[] values0 = toArray(values00);
				line = caches[0].getCacheLine(memoryAddress);
				caches[0].data.put(line, values0);
				caches[0].usage[line]++;
				if (caches[0].dirtyBits[line] == 1) {
					caches[0].dirtyBits[line] = 0;
					addTime = true;
				}
			}
		}
	}

	public static int writeToMemory(int memoryAddress, int value) {
		memory.put(new Integer(memoryAddress), new Integer(value));
		int time = 0;
		boolean found = false;
		for (int i = 0; i < caches.length && !found; i++) {
			Set<Integer> keys = caches[i].data.keySet();
			Object[] lineNumbers = keys.toArray();
			for (int j = 0; j < lineNumbers.length && !found; j++) {
				for (int k = 0; k < caches[i].data.get(lineNumbers[j]).length
						&& !found; k++) {
					if (caches[i].data.get(lineNumbers[j])[k] == memoryAddress) {
						found = true;
						if (caches[i].writePolicy == 0) {
							time = (Integer) computeTime(i);
							caches[i].dirtyBits[((Integer) lineNumbers[j])
									.intValue()] = 1;
							addFromCacheToCache(memoryAddress, i, j);
						} else {
							time = (Integer) computeTime(cacheLevels)
									+ memoryAccessTime;
						}
						caches[i].usage[((Integer) lineNumbers[j]).intValue()]++;
						computeAccessesAndMisses(memoryAddress, i);
					}
				}
			}
		}
		if (!found) {
			computeAccessesAndMisses(memoryAddress, 4);
			time = (Integer) computeTime(cacheLevels) + memoryAccessTime;
			if (caches[cacheLevels].writePolicy == 0)
				cacheMemoryBlock(memoryAddress);
		}
		if (addTime) {
			time += memoryAccessTime;
			addTime = false;
		}
		return time;
	}
}
